package gormzaplogger

import (
	"context"
	"go.uber.org/zap"
	glogger "gorm.io/gorm/logger"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

type Logger struct {
	ZapLogger *zap.Logger
	Config    Config
}

type Config struct {
	LogLevel         glogger.LogLevel
	SlowThreshold    time.Duration
	SkipCallerLookup bool
}

func New(logger *zap.Logger, config Config) Logger {
	if config.LogLevel == 0 {
		config.LogLevel = glogger.Warn
	}
	if config.SlowThreshold == 0 {
		config.SlowThreshold = 100 * time.Millisecond
	}

	return Logger{
		ZapLogger: logger,
		Config:    config,
	}
}

func LogLevel(level string) glogger.LogLevel {
	switch level {
	case "info", "debug":
		return glogger.Info
	case "warn":
		return glogger.Warn
	case "error", "fatal", "panic":
		return glogger.Error
	default:
		return glogger.Info
	}
}

func (l Logger) SetAsDefault() {
	glogger.Default = l
}

func (l Logger) LogMode(level glogger.LogLevel) glogger.Interface {
	return Logger{
		ZapLogger: l.ZapLogger,
		Config: Config{
			SlowThreshold:    l.Config.SlowThreshold,
			LogLevel:         level,
			SkipCallerLookup: l.Config.SkipCallerLookup,
		},
	}
}

func (l Logger) Info(ctx context.Context, str string, args ...interface{}) {

	if l.Config.LogLevel < glogger.Info {
		return
	}
	l.logger().Sugar().Debugf(str, args...)
}

func (l Logger) Warn(ctx context.Context, str string, args ...interface{}) {
	if l.Config.LogLevel < glogger.Warn {
		return
	}
	l.logger().Sugar().Warnf(str, args...)
}

func (l Logger) Error(ctx context.Context, str string, args ...interface{}) {
	if l.Config.LogLevel < glogger.Error {
		return
	}
	l.logger().Sugar().Errorf(str, args...)
}

func (l Logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if l.Config.LogLevel <= 0 {
		return
	}
	elapsed := time.Since(begin)
	switch {
	case err != nil && l.Config.LogLevel >= glogger.Error:
		sql, rows := fc()
		l.logger().Error("trace", zap.Error(err), zap.Duration("elapsed", elapsed), zap.Int64("rows", rows), zap.String("sql", sql))
	case l.Config.SlowThreshold != 0 && elapsed > l.Config.SlowThreshold && l.Config.LogLevel >= glogger.Warn:
		sql, rows := fc()
		l.logger().Warn("trace", zap.Duration("elapsed", elapsed), zap.Int64("rows", rows), zap.String("sql", sql))
	case l.Config.LogLevel >= glogger.Info:
		sql, rows := fc()
		l.logger().Debug("trace", zap.Duration("elapsed", elapsed), zap.Int64("rows", rows), zap.String("sql", sql))
	}
}

func (l Logger) logger() *zap.Logger {
	gormPackage := filepath.Join("gorm.io", "gorm")
	for i := 2; i < 15; i++ {
		_, file, _, ok := runtime.Caller(i)
		switch {
		case !ok:
		case strings.HasSuffix(file, "_test.go"):
		case strings.Contains(file, gormPackage):
		default:
			return l.ZapLogger.WithOptions(zap.AddCallerSkip(i))
		}
	}
	return l.ZapLogger
}
